(function($)
{
	/**
	 * 
	 * @param configs
	 */
	$.fn.uploader = function(configs)
	{
		this.configs = new Object();
		
		// 환경 설정 구성
		// * uploaderId : 업로더 아이디(파일 업로더의 고유 인덱스로 파일 업로더 여러 개가 동시에 사용될 경우 구분하기 위해 사용된다.)
		// * url : 업로드 URL
		// * inputId : 파일 Input 아이디
		// * accept : 허용할 contents type
		// * partMaxLength : 한 blob당 가질 수 있는 최대 크기
		// * limitFileCount : 한번에 업로드가 가능한 파일의 개수(0이하: 무제한, 1: 단일, n: n개)
		// * data : 커스텀 데이터
		// * success : 업로드 완료 이벤트
		// * fail : 업로드 실패 이벤트
		// * progress : 업로드 진행 이벤트
		// * choose : 업로드 파일 선택 이벤트
		// * exceed : 업로드 파일 제한 초과 이벤트
		jQuery.extend(this.configs, configs);
		
		this.configs.uploaderId || (this.configs.uploaderId = 0);
		this.configs.partMaxLength || (this.configs.partMaxLength = 10 * 1024 * 1024); // 기본 10Mb
		this.configs.limitFileCount || (this.configs.limitFileCount = 1); // 단일 업로드
		
		$.prototype.prepare = function()
		{
			var transparentDom = $('<div></div>').appendTo(this).css
			(
				{
					position : 'relative',
					overflow : 'hidden',
					opacity : 0,
					'z-index' : 99,
					top : 0,
					left : 0,
					width : 'auto',
					height : 'auto'
				}
			);
			
			var multiple = this.configs.limitFileCount != 1 ? ' multiple="multiple"' : '';
			var accept = this.configs.accept ? ' accept="' + this.configs.accept + '"' : '';
			
			// 파일 Input을 만들고 투명 div에 append 하고 버튼의 크기와 동일하게 설정한다.
			this.fileInput = $('<input type="file"' + multiple + accept + '></input>').appendTo(transparentDom).css
			(
				{
					margin : 0,
					width : 'auto',
					height : 'auto'
				}
			);
			
			this.fileInput.bind('change', {configs : this.configs}, this.onChoosed);
		};
		
		$.prototype.onChoosed = function(e)
		{
			var files = e.target.files;
			var count = files.length;
			
			var configs = e.data.configs;
			
			if (configs.limitFileCount == 0)
			{
				if (configs.exceed)
				{
					configs.exceed();
				}
				
				return;
			}
			
			if (configs.limitFileCount > 0)
			{
				configs.limitFileCount -= count;
			}
			
			if (configs.choose)
			{
				configs.choose(count, configs.limitFileCount);
			}
			
			for (var index = 0; index < count; index++)
			{
				// index는 파일의 번호로 이용된다. 멀티 업로드를 사용하는 경우, 파일을 구분하기 위해 사용된다.
				var handler = new $.MultipartHandler(configs, files[index], index);
				
				handler.slice();
			}
		};
		
		this.prepare();
	};
	
	/**
	 * Multipart
	 * 파일을 서버 측으로 전송한다.
	 */
	$.MultipartHandler = function(configs, file, fileNo)
	{
		this.configs = configs;
		
		this.file = file; // 전송할 파일
		this.uploaderId = this.configs.uploaderId; // 패키지 아이디
		this.fileNo = fileNo; // 파일 번호
		
		this.total = file.fileSize ? file.fileSize : file.size; // 파일의 전체 크기
		this.sliced = 0; // 전송을 시도한 blob들의 크기
		this.sended = 0; // 전송된 blob들의 크기
		
		this.blobCount = parseInt(this.total / this.configs.partMaxLength) + 1; // 전체 Blob의 개수
		this.blobIndex = 0; // 전송할 blob의 순번
		
		this.xhr = new XMLHttpRequest();
		
		$(this.xhr).bind('load', {handler: this, uploaderId: this.uploaderId, fileNo: this.fileNo, customs: configs.customs}, this.onSend);
		$(this.xhr).bind('error', {handler: this, uploaderId: this.uploaderId, fileNo: this.fileNo, customs: configs.customs}, this.onError);
		
		// progress 이벤트는 XHR의 Upload와 연결해야 전송률을 알 수 있다.
		$(this.xhr.upload).bind('progress', {handler: this, uploaderId: this.uploaderId, fileNo: this.fileNo, customs: configs.customs}, this.onProgress);
	};
	
	$.extend($.MultipartHandler.prototype,
	{
		/**
		 * 전송할 파일을 단위 크기로 잘라낸다.
		 */
		slice : function()
		{
			var startIndex = this.sliced; // 전송 시작 인덱스
			var offset = this.total - this.sliced < this.configs.partMaxLength ? this.total : this.sliced + this.configs.partMaxLength; // 전송 마지막 인덱스
			
			// 다음 blob을 위해 전송을 시도한 blob 크기를 더해준다.
			this.sliced += this.total - this.sliced < this.configs.partMaxLength ? this.total : this.configs.partMaxLength;
			
			// 파일을 전송을 위한 위치의 blob으로 토막낸다.
			var blob = this.file.webkitSlice ? this.file.webkitSlice(startIndex, offset) :
					this.file.mozSlice ? this.file.mozSlice(startIndex, offset) :
					this.file.slice(startIndex, offset);
			
			this.blobIndex++;
			
			this.send(blob);
		},
		
		/**
		 * Blob을 서버로 전송한다.
		 */
		send : function(blob)
		{
			var formData = new FormData();
			
			// FormData에 전송하려는 blob을 추가한다.
			formData.append(this.configs.inputId, blob);
			
			// 패키지 아이디와 파일 번호 조합으로 파일 아이디를 전송한다.
			// 이것은 하나의 업로더를 이용해 여러 파일을 올릴 때 파일의 크기가 큰 경우 BLOB 단위 전송을 위해서 사용된다.
			formData.append('fileId', this.uploaderId + '_' + this.fileNo);
			formData.append('fileSize', this.total); // 파일 크기
			formData.append('fileName', this.file.name); // 파일의 실제 명칭(확장자 포함)
			formData.append('blobIndex', this.blobIndex); // 전송할 blob의 인덱스
			formData.append('blobCount', this.blobCount); // blob의 개수
			
			this.xhr.open('post', this.configs.url, true);
			
			this.xhr.send(formData);
		},
		
		onSend : function(e)
		{
			// JSON 형식을 처리한다.
			e.response = JSON.parse(e.target.response || e.target.responseText);
			
			switch (e.response.result)
			{
				// Success :)
				case true:
				
					e.data.handler.sended += e.response.totalSize;
					
					var blobCount = e.response.multipart.blobCount;
					var blobIndex = e.response.multipart.blobIndex;
					
					if (blobCount == blobIndex && e.data.handler.configs.success)
					{
						e.data.handler.configs.success(e);
					}
					else if (blobCount > blobIndex)
					{
						e.data.handler.slice();
					}
					
					break;
					
				// Fail :(
				case false:
				
					e.data.handler.error(e);
				
					break;
			}
		},
		
		onProgress : function(e)
		{
			e.blobLoaded = e.originalEvent.loaded;
			e.blobTotal = e.originalEvent.total;
			
			e.loaded = e.data.handler.sended + e.blobLoaded;
			e.total = e.data.handler.total;
			
			if (e.data.handler.configs.progress)
			{
				e.data.handler.configs.progress(e);
			}
		},
		
		onError : function(e)
		{
			// JSON 형식을 처리한다.
			e.response = JSON.parse(e.target.response || e.target.responseText);
		}
	});
})(jQuery);